package com.sh.rbac.utils;

import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sh.rbac.exception.CustomException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.core.io.ClassPathResource;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * IP地址Util
 */
@Slf4j
public class IpAddressUtils {


    /**
     * 获取IP地址
     */
    public static String getIpAddress(HttpServletRequest request) {
        String ipAddress = request.getHeader("x-forwarded-for");
        if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress =request.getRemoteAddr();

            if ("127.0.0.1".equals(ipAddress) || "0:0:0:0:0:0:0:1".equals(ipAddress)) {
                // 根据网卡取本机配置的IP
                try {
                    InetAddress inet = InetAddress.getLocalHost();
                    ipAddress = inet.getHostAddress();
                } catch (Exception e) {
                    log.error("获取IP地址异常,{}", e.getMessage());
                }
            }
        }

        // 对于通过多个代理的情况，第一个IP为客户端真实IP，多个IP按照','分割
        if (ipAddress != null && ipAddress.indexOf(",") > 0) {
            ipAddress = ipAddress.split(",")[0];
        }

        return ipAddress;
    }

    /**
     * 获取mac地址
     */
    public static String getMacIpAddress() {
        try {
            InetAddress inetAddress = InetAddress.getLocalHost();
            byte[] macAddressBytes = NetworkInterface.getByInetAddress(inetAddress).getHardwareAddress();
            // 将mac地址拼装成String
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < macAddressBytes.length; i++) {
                if (i != 0) {
                    sb.append("-");
                }
                // mac[i] & 0xFF 是为了把byte转化为正整数
                String s = Integer.toHexString(macAddressBytes[i] & 0xFF);
                sb.append(s.length() == 1 ? 0 + s : s);
            }
            return sb.toString().trim().toUpperCase();
        } catch (Exception e) {
            log.error("Mac获取IP地址异常,{}", e.getMessage());
        }
        return "";
    }

    /**
     * 方法一：完全基于ip2region.xdb文件，对用户ip地址进行转换
     * 注：并发调用时，每个线程需创建一个独立的searcher对象 单独使用。
     */
    public static String getIpPossessionByFile(String ip) {
        if (StrUtil.isNotEmpty(ip)) {
            try {
                // 1、创建 searcher 对象
                Searcher searcher = Searcher.newWithBuffer(ResourceUtil.readBytes("ip2region.xdb"));
                // 2、查询
                long sTime = System.nanoTime();
                String region = searcher.search(ip);
                long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);
                region = region.replace("|0", "");
                //log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost);
                return region;
            } catch (Exception e) {
                log.error("获取IP地址异常：{} ", e.getMessage());
                throw new RuntimeException("获取IP地址异常");
            }
        }
        return "未知";
    }

    /**
     * 方法二：缓存 VectorIndex 索引，对用户ip地址进行转换
     * 注：每个线程需要单独创建一个独立的 Searcher 对象，但是都共享全局变量 vIndex 缓存。
     */
    public static String getCityInfoByVectorIndex(String ip) {
        if (StrUtil.isNotEmpty(ip)) {
            try {
                // 1、从 XDB_PATH 中预先加载 VectorIndex 缓存，并且作为全局变量，后续反复使用。
                byte[] vIndex = Searcher.loadVectorIndexFromFile(getXdbPath());
                // 2、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
                Searcher searcher = Searcher.newWithVectorIndex(getXdbPath(), vIndex);
                // 3、查询
                long sTime = System.nanoTime();
                String region = searcher.search(ip);
                long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);
                region = region.replace("|0", "");
                //log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost);
                return region;
            } catch (Exception e) {
                log.error("获取IP地址异常：{} ", e.getMessage());
                throw new RuntimeException("获取IP地址异常");
            }
        }
        return "未知";
    }

    /**
     * 方法三：缓存整个 xdb 数据，对用户ip地址进行转换
     * 注：并发使用时，用整个 xdb 数据缓存创建的查询对象可以安全的用于并发，也就是你可以把这个 searcher 对象做成全局对象去跨线程访问。
     */
    public static String getCityInfoByMemorySearch(String ip) {
        if (StrUtil.isNotEmpty(ip)) {
            try {
                // 1、从 XDB_PATH 加载整个 xdb 到内存。
                byte[] cBuff = Searcher.loadContentFromFile(getXdbPath());
                // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
                Searcher searcher = Searcher.newWithBuffer(cBuff);
                // 3、查询
                long sTime = System.nanoTime();
                String region = searcher.search(ip);
                long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);
                region = region.replace("|0", "");
                //log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost);
                return region;
            } catch (Exception e) {
                log.error("获取IP地址异常：{} ", e.getMessage());
                throw new RuntimeException("获取IP地址异常");
            }
        }
        return "未知";
    }

    /**
     * 方法四：在线获取IP地址
     * 注：通过别人或者官网提供的API接口去实现查询的功能，弊端就是特别依赖别人的服务器，一旦服务器宕机就无法访问了。
     */
    public static String getIpAddressByOnline(String ip) {

        try {
            //1、创建 URLConnction
            URL url = new URL("http://ip-api.com/json/" + ip + "?lang=zh-CN");

            //2、设置connection的属性
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(20000);
            connection.setReadTimeout(20000);
            connection.setRequestProperty("content-type", "application/json; charset=utf-8");

            //3.连接
            connection.connect();

            //4.获取内容
            InputStream inputStream = connection.getInputStream();

            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
            String line;
            StringBuilder sb = new StringBuilder();
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            br.close();
            //System.out.println(sb);

            String str = sb.toString();
            if (StrUtil.isNotEmpty(str)) {
                Map map = new ObjectMapper().readValue(str, Map.class);
                String country = (String) map.get("country");
                String city = (String) map.get("city");
                String regionName = (String) map.get("regionName");
                //log.info("【国家】{}，【城市】{}，【地区】{}", country, city, regionName);
                return country + "|" + city + "|" + regionName;
            }
        } catch (Exception e) {
            log.error("在线查询IP地址异常，{}", e.getMessage());
            throw new RuntimeException("在线查询IP地址异常");
        }
        return null;
    }

    /**
     * 根据IP地址 获取归属地
     */
    public static String getIpPossession(String ipAddress) {
        if (StrUtil.isNotEmpty(ipAddress)) {
            ipAddress = ipAddress.replace("|", " ");
            String[] cityList = ipAddress.split(" ");
            if (cityList.length > 0) {
                // 国内的显示到具体的省
                if ("中国".equals(cityList[0])) {
                    if (cityList.length > 1) {
                        return cityList[1];
                    }
                }
                // 国外显示到国家
                return cityList[0];
            }
        }
        return "未知";
    }


    /**
     * 获取 xdb 文件路径
     */
    private static String getXdbPath() {
        try {
            // 获取文件路径
            ClassPathResource classPathResource = new ClassPathResource("ip2region.xdb");
            return classPathResource.getFile().getAbsolutePath();
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException("获取 ip2region.xdb 文件失败");
        }
    }


    public static void main(String[] args) {
        String ip = "119.143.74.67";// 国内IP
        String abroadIp = "6.6.6.6"; // 国外IP

        System.out.println("方法一（国内）：" + getIpPossessionByFile(ip));
        System.out.println("方法二（国内）：" + getCityInfoByVectorIndex(ip));
        System.out.println("方法三（国内）：" + getCityInfoByMemorySearch(ip));
        System.out.println("方法四（国内）：" + getIpAddressByOnline(ip));

        System.out.println("方法一（国外）：" + getIpPossessionByFile(abroadIp));
        System.out.println("方法二（国外）：" + getCityInfoByVectorIndex(abroadIp));
        System.out.println("方法三（国外）：" + getCityInfoByMemorySearch(abroadIp));
        System.out.println("方法四（国外）：" + getIpAddressByOnline(abroadIp));

        System.out.println("归属地（国内）：" + getIpPossession(getCityInfoByVectorIndex(ip)));
        System.out.println("归属地（国外）：" + getIpPossession(getCityInfoByVectorIndex(abroadIp)));
    }
}